﻿/**
 * Peter
 * Created by: Peter Development Team
 *    http://peter.codeplex.com/
 * 
 * GNU General Public License version 2 (GPLv2)
 *    http://peter.codeplex.com/license
 *
 *  This code is provided on an AS IS basis, with no WARRANTIES,
 *  CONDITIONS or GUARANTEES of any kind.
 *
 **/

using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel.Composition;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Input;
using Peter.Common;
using Peter.Common.BindingConverters;
using Peter.Common.Icons;
using Peter.Common.Interfaces;
using Peter.Common.MainMenu;
using Peter.Common.Services;
using Peter.Common.Tree;
using Peter.Data;

namespace Peter.Logic
{
   /// <summary>
   /// Represents a peter workspace.
   /// </summary>
   public sealed class ModelWorkspace : EditablePeterToolWindowWithMenu, IPartImportsSatisfiedNotification
   {
      private readonly ModelProjectManager m_ProjectManager;
      private EditableTreeViewItem m_SelectedItem;
      private RelayCommand m_CmdProperties;
      private WorkspaceOptions m_Options;
      private readonly RelayCommand m_ExecuteMenuItem;
      private readonly BooleanToVisibilityConverter m_BoolToVisi;

      /// <summary>
      /// Initializes a new workspace.
      /// </summary>
      public ModelWorkspace (ModelProjectManager projectManager)
      {
         this.Title = "Workspace";
         this.Icon = MainMenuIcon.Linux;
         this.ContentId = Guids.WORKSPACE;
         this.ToolTip = "Manage open projects";
         this.m_ProjectManager = projectManager;
         this.m_BoolToVisi = new BooleanToVisibilityConverter();
         this.Projects = new ObservableCollection <PeterProjectItem> ();

         this.ContextMenu = new ContextMenu ();
         this.m_ExecuteMenuItem = new RelayCommand(this.OnExecuteMenuItem, this.CanExecuteMenuItem);
         this.ContextMenu.SetBinding (FrameworkElement.DataContextProperty, new Binding ("SelectedItem") { Source = this });
      }

      /// <summary>
      /// Gets the options.
      /// </summary>
      [Import (typeof (IPeterOptions))]
      public IPeterOptions PeterOptions { get; set; }

      /// <summary>
      /// Gets or Sets the selected tree item.
      /// </summary>
      public EditableTreeViewItem SelectedItem
      {
         get { return m_SelectedItem; }
         set
         {
            this.m_SelectedItem = value;
            this.OnPropertyChanged ("SelectedItem");
         }
      }

      /// <summary>
      /// Gets the list of menu items.
      /// </summary>
      [ImportMany ("WorkspaceMenuItem")]
      public IEnumerable<PeterContextMenuItem> MenuItems { get; set; }

      /// <summary>
      /// Gets the list of project associated with this workspace.
      /// </summary>
      public ObservableCollection<PeterProjectItem> Projects { get; private set; }

      /// <summary>
      /// Gets the context menu.
      /// </summary>
      public ContextMenu ContextMenu { get; private set; }

      /// <summary>
      /// Gets the current project
      /// </summary>
      public PeterProjectItem CurrentProject { get; private set; }

      /// <summary>
      /// Gets the properties command.
      /// </summary>
      public ICommand PropertiesCommand
      {
         get
         {
            return this.m_CmdProperties ?? (this.m_CmdProperties = new RelayCommand (
               delegate
               {
                  var fileItem = this.SelectedItem as FileTreeViewItem;
                  if (fileItem != null && fileItem.File != null)
                     CommonUtilities.ShowFileProperties (fileItem.File.FullName);
               },
               delegate
               {
                  return this.SelectedItem is FileTreeViewItem;
               }));
         }
      }

      /// <summary>
      /// Checks to see if the currently selected item can be set as a current project or not.
      /// </summary>
      /// <param name="obj"></param>
      /// <returns></returns>
      private bool CanBeCurrentProject (object obj)
      {
         var proj = this.SelectedItem as PeterProjectItem;
         return proj != null && proj.CanBeCurrentProject;
      }

      /// <summary>
      /// Sets the current item as the current project.
      /// </summary>
      private void SetCurrentProject (object obj)
      {
         foreach (var peterProject in this.Projects)
         {
            peterProject.SetCurrentProject(false);
         }
         var project = (PeterProjectItem) this.SelectedItem;
         project.IsCurrentProject = true;
         this.CurrentProject = project;
      }

      /// <summary>
      /// Opens the given project in the current workspace.
      /// </summary>
      /// <param name="peterProject">Project to open.</param>
      public void Open (PeterProjectItem peterProject)
      {
         if (this.m_Options.OpenProjects.Any (wp => wp.ProjectLocation == peterProject.File.FullName))
         {
            var status = ServiceManager.Get <IStatus> ();
            status.SetStatus ("The project is already open!", StatusTimeDelay.Medium, StatusType.Warning);
            return;
         }

         this.Peter.ShowToolWindow (PeterToolWindows.Workspace);
         this.Projects.Add (peterProject);
         this.m_Options.OpenProjects.Add (new WorkspaceProject
                                             {
                                                ProjectLocation = peterProject.File.FullName,
                                                ProjectType = peterProject.GetType ().ToString ()
                                             });
         this.PeterOptions.Save ();
      }

      /// <summary>
      /// Occurs when a tree item is being executed.
      /// </summary>
      /// <param name="obj">Command parameter.</param>
      protected override void OnExecute (object obj)
      {
         var item = this.SelectedItem as PeterProjectItem;
         if (item != null && !item.IsDirectory)
         {
            this.Peter.Open (item);
         }
      }

      /// <summary>
      /// Checks whether an edit actions can be performed or not.
      /// </summary>
      /// <param name="obj">Edit action</param>
      /// <returns>True if action can be performed, otherwise false.</returns>
      protected override bool CanEdit (object obj)
      {
         if (this.SelectedItem == null) return false;

         var editAction = (EditAction)obj;
         switch (editAction)
         {
            case EditAction.Copy:
               return this.SelectedItem.EditCommand.CanExecute (EditAction.Copy);
            case EditAction.Cut:
               return this.SelectedItem.EditCommand.CanExecute (EditAction.Cut);
            case EditAction.Paste:
               return this.SelectedItem.EditCommand.CanExecute (EditAction.Paste);
            case EditAction.SelectAll:
               return true;
            case EditAction.Save:
               return this.NeedsSaving;
            default:
               return false;
         }
      }

      /// <summary>
      /// Performs the edit action.
      /// </summary>
      /// <param name="obj">Edit action.</param>
      protected override void OnEdit (object obj)
      {
         var editAction = (EditAction)obj;
         switch (editAction)
         {
            case EditAction.Copy:
               this.SelectedItem.EditCommand.Execute (EditAction.Copy);
               break;
            case EditAction.Cut:
               this.SelectedItem.EditCommand.Execute (EditAction.Cut);
               break;
            case EditAction.Paste:
               this.SelectedItem.EditCommand.Execute (EditAction.Paste);
               break;
            case EditAction.Save:
               break;
         }
      }

      /// <summary>
      /// Called when a part's imports have been satisfied and it is safe to use.
      /// </summary>
      public async void OnImportsSatisfied ()
      {
         this.m_Options = this.PeterOptions.GetOptionData (WorkspaceOptions.WORK_SPACE_OPTION_NAME) as WorkspaceOptions;
         if (this.m_Options != null)
         {
            foreach (var workspaceProject in this.m_Options.OpenProjects)
            {
               var project = this.m_ProjectManager.GetProjectForType (workspaceProject.ProjectType);
               this.Projects.Add (await project.Open (workspaceProject.ProjectLocation));
            }
         }

         var fileTreeViewTypeConverter = new TypeVisibilityConverter <FileTreeViewItem> ();
         var menuItem = new MenuItem
         {
            Header = "Set as Current Project",
            Icon = new IconDisplay { Icon = MainMenuIcon.News },
            Command = new RelayCommand (this.SetCurrentProject, this.CanBeCurrentProject)
         };
         menuItem.SetBinding (UIElement.VisibilityProperty,
                          new Binding { Converter = new TypeVisibilityConverter<PeterProjectItem> () });
         this.ContextMenu.Items.Add (menuItem);

         menuItem = new MenuItem
         {
            Header = "Edit",
            Icon = new IconDisplay { Icon = MainMenuIcon.Edit },
            Command = new RelayCommand (this.OpenCurrentItem, this.CanOpenCurrentItem)
         };
         menuItem.SetBinding (UIElement.VisibilityProperty,
                          new Binding { Converter = fileTreeViewTypeConverter });
         this.ContextMenu.Items.Add (menuItem);

         int a = 99;
         foreach (var item in this.MenuItems.OrderBy (m => m.Priority))
         {
            if (item.Priority > a)
            {
               this.ContextMenu.Items.Add (new Separator ());
               a += 100;
            }
            this.ContextMenu.Items.Add (this.GenerateMenuItem(item));
         }

         menuItem = new MenuItem
         {
            Header = "Properties",
            Icon = new IconDisplay { Icon = MainMenuIcon.Reliability },
            Command = this.PropertiesCommand
         };
         menuItem.SetBinding (UIElement.VisibilityProperty,
                          new Binding { Converter = fileTreeViewTypeConverter });
         this.ContextMenu.Items.Add (menuItem);

         this.Peter.ShuttingDown += this.OnShutDown;
      }

      private MenuItem GenerateMenuItem (PeterContextMenuItem pMenuItem)
      {
         var menuItem = new MenuItem {Command = this.m_ExecuteMenuItem, CommandParameter = pMenuItem};
         menuItem.SetBinding (HeaderedItemsControl.HeaderProperty, new Binding ("Title") { Source = pMenuItem });
         menuItem.SetBinding (UIElement.VisibilityProperty, new Binding ("IsVisible") { Source = pMenuItem, Converter = this.m_BoolToVisi});

         var iconDisplay = new IconDisplay ();
         iconDisplay.SetBinding (IconDisplay.IconProperty, new Binding ("Icon") {Source = pMenuItem});
         menuItem.Icon = iconDisplay;

         return menuItem;
      }

      private bool CanExecuteMenuItem (object obj)
      {
         var item = obj as PeterContextMenuItem;
         if (item != null)
            return item.Command.CanExecute (this.SelectedItem);
         return false;
      }

      private void OnExecuteMenuItem (object obj)
      {
         var item = obj as PeterContextMenuItem;
         if (item != null)
            item.Command.Execute (this.SelectedItem);
      }

      /// <summary>
      /// Checks to see if the open current item command can execute.
      /// </summary>
      /// <param name="obj">Command parameter.</param>
      /// <returns>True if command can execute, otherwise false.</returns>
      private bool CanOpenCurrentItem (object obj)
      {
         var selectedItem = this.SelectedItem as FileTreeViewItem;
         if (selectedItem != null)
            return !selectedItem.IsDirectory;
         return false;
      }

      /// <summary>
      /// Opens the current item in peter.
      /// </summary>
      /// <param name="o"></param>
      private void OpenCurrentItem (object o)
      {
         var selectedItem = this.SelectedItem as FileTreeViewItem;
         if (selectedItem != null)
         {
            this.Peter.Open (selectedItem.File.FullName);
         }
      }

      /// <summary>
      /// Occurs when peter is shutting down.
      /// </summary>
      /// <param name="sender">Peter</param>
      /// <param name="e">EventArgs</param>
      private void OnShutDown (object sender, EventArgs e)
      {
         this.Peter.ShuttingDown -= this.OnShutDown;
         foreach (var peterProject in this.Projects)
         {
            peterProject.PeterClosing ();
         }
      }
   }
}
